#!/usr/bin/env python  
# -*- coding: utf-8 -*-  
import errno  
import fuse  
import stat  
import os  
import sys
import time  
import tempfile
import json
  
fuse.fuse_python_api = (0, 2)  
  
# Use same timestamp for all files  
_file_timestamp = int(time.time())  

sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '../..'))
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..'))

from django.core.management import setup_environ
from django.conf import settings

setup_environ(settings)

from assets.models import Asset
from files.models import File


class FileEncoder(json.JSONEncoder):
  def default(self, obj):
    if isinstance(obj, tempfile._TemporaryFileWrapper):
      return obj.name
    return json.JSONEncoder.default(self, obj)

class Asset():  
  def __init__(self, name, file, deps=[]): 
    self.name = name
    self.file = file
    self.dependencies = deps
  
class MyStat(fuse.Stat):  
  """ 
  Convenient class for Stat objects. 
  Set up the stat object with appropriate 
  values depending on constructor args. 
  """  
  def __init__(self, is_dir, size):  
    fuse.Stat.__init__(self)  
    if is_dir:  
      self.st_mode = stat.S_IFDIR | 0555  
      self.st_nlink = 2  
    else:  
      self.st_mode = stat.S_IFREG | 0444  
      self.st_nlink = 1  
      self.st_size = size  
    self.st_atime = _file_timestamp  
    self.st_mtime = _file_timestamp  
    self.st_ctime = _file_timestamp  

class DAMNFileDirectoryStat(fuse.Stat):   
  def __init__(self, file):  
    fuse.Stat.__init__(self)   
    self.st_mode = stat.S_IFDIR | 0555  
    self.st_nlink = 2   
    stamp = int(time.mktime(file.stamp.timetuple()))
    self.st_atime = stamp  
    self.st_mtime = stamp  
    self.st_ctime = stamp 


class DAMNFileStat(fuse.Stat):   
  def __init__(self, file):  
    fuse.Stat.__init__(self)   
    self.st_mode = stat.S_IFREG | 0444  
    self.st_nlink = 1  
    self.st_size = file.location.size   
    stamp = int(time.mktime(file.stamp.timetuple()))
    self.st_atime = stamp  
    self.st_mtime = stamp  
    self.st_ctime = stamp
    
          
class MyFS(fuse.Fuse):  
  def __init__(self, *args, **kw):  
    fuse.Fuse.__init__(self, *args, **kw)  
    self.assets = [Asset('1', '/home/sueastside/damn/test.py', [Asset('1a', 'ola/blah/file.png'), Asset('1b', 'blah/file.png')]), 
                   Asset('3', 'rr/3.blend')] 
  
    self.nodes = {}
    
  def find(self, hash):
    try:
      file = File.objects.get(hash=hash)
    except File.DoesNotExist:
      file = None
    return file
  
  def find_node(self, path):
    s = path.split('/')[1:]
    nodes = self.nodes
    for p in s:
      if p in nodes:
        if p == s[-1]:
          return nodes[p]
        nodes = nodes[p]['children']
        
  def create_node(self, path, is_dir):
    s = path.split('/')[1:]
    nodes = self.nodes
    for p in s:
      if p in nodes:  
        if p == s[-1]:
          raise Exception('Node %s already exists'%(path))
        nodes = nodes[p]['children']
      else:
        nodes[p] = {'children':{}, 'directory': not (p == s[-1] and not is_dir), 'file': None}
        if p == s[-1]:
          return nodes[p]
        nodes = nodes[p]['children']
  
  def get_file(self, path, dir_func, file_func, error_func):
    p = path[1:]
    pos = p.find('/')
    if pos == -1:
      file = self.find(p)
      if not file: return error_func()
      return dir_func(file)
    else:
      file = self.find(p[:pos])
      if not file: return error_func()
      base_path = p[pos+1:]
      if base_path == os.path.basename(file.file_name):
        return file_func(file)
      else:
        for asset in file.asset_set.all():
          for dep in asset.dependencies.all():
            if base_path == os.path.basename(dep.file.file_name):
              return file_func(dep.file)
            else:
              for path in dep.file.filepath_set.all():
                if path.file_path.startswith(base_path):
                  return dir_func(dep.file)
  
  def getattr(self, path):
    #print '*** getattr', path
    def dir(file):
      return DAMNFileDirectoryStat(file)
    def file(file):
      return DAMNFileStat(file)
    def error():
      return -errno.ENOENT
    
    if path == '/':
      return MyStat(True, 0)
      
    if path == '/config':
      content = json.dumps(self.nodes, indent=4, cls=FileEncoder)
      return MyStat(False, len(content))
       
    node = self.find_node(path)
    if node:
      if node['directory']:
        return MyStat(True, 0)
      else:
        size = os.stat(node['file'].name).st_size if node['file'] else 0
        #print 'FOUND', path
        return MyStat(False, size)
    
    return self.get_file(path, dir, file, error)

  def readdir(self, path, offset):  
    print '*** readdir', path, offset
    yield fuse.Direntry('.')  
    yield fuse.Direntry('..')  
    if path == '/':
      yield fuse.Direntry('config') 
      files = File.objects.all()
      for file in files:
        yield fuse.Direntry(str(file.hash))  
    else:
      p = path[1:]
      pos = p.find('/')
      if pos == -1:
        file = self.find(p)
        yield fuse.Direntry(file.file_name)
        for asset in file.asset_set.all():
          for dep in asset.dependencies.all():
            for path in dep.file.filepath_set.all():
              if not path.file_path.startswith('/'):
                yield fuse.Direntry(path.file_path[:path.file_path.find('/')])
        #for dep in asset.dependencies:
        #  yield fuse.Direntry(dep.file[:dep.file.find('/')])
      else:
        file = self.find(p[:pos])
        '''
        for dep in asset.dependencies:
          base_path = p[pos+1:]
          if base_path == os.path.dirname(dep.file):
            yield fuse.Direntry(os.path.basename(dep.file))
            for d in dep.dependencies:
              yield fuse.Direntry(d.file[:d.file.find('/')])
          elif dep.file.startswith(base_path):
            r = dep.file[len(base_path)+1:]
            r = r[:r.find('/')]
            yield fuse.Direntry(r)
        '''
  
  def open(self, path, flags):  
    #print '*** open', path, flags
    
    if path == '/config':
      return 0
      
    node = self.find_node(path)
    if node:
      return 0
    # Only support for 'READ ONLY' flag  
    access_flags = os.O_RDONLY | os.O_WRONLY | os.O_RDWR  
    if flags & access_flags != os.O_RDONLY:  
      return -errno.EACCES  
    else:  
      return 0  
  
  def read(self, path, size, offset):  
    #print '*** read', path, size, offset
    def dir(asset):
      return ''
    def file(asset, size=size):
      file_size = os.stat(asset.file).st_size
      if offset < file_size:  
        if offset + size > file_size:  
          size = file_size - offset
        f = open(asset.file, 'rb')
        f.seek(offset)
        data = f.read(size)
        f.close()
        return data
      else: 
        return ''  
    def error():
      return ''
    
    if path == '/config':
      content = json.dumps(self.nodes, indent=4, cls=FileEncoder)
      file_size = len(content)
      if offset < file_size:  
        if offset + size > file_size:  
          size = file_size - offset
        return content[offset:offset+size]
      else: 
        return ''
        
    node = self.find_node(path)
    if node:
      f = node['file']
      f.seek(offset)
      return f.read(size)
      
    return self.get_file(path, dir, file, error)
    
  def mkdir ( self, path, mode ):
    #print '*** mkdir', path, oct(mode)
    self.create_node(path, True)
    print self.nodes

  def mknod ( self, path, mode, dev ):
    #print '*** mknod', path, oct(mode), dev
    if stat.S_ISREG(mode):
        node = self.create_node(path, False)
        node['file'] = tempfile.NamedTemporaryFile()
        print node['file'].__class__, self.nodes
    else:
        return -EINVAL
  
  def unlink(self, path):
    print '*** unlink', path
    if path == '/config':
      self.nodes = {}
      return 
    return -errno.ENOSYS
    
  def write ( self, path, buf, offset ):
    #print '*** write', path, buf, offset
    return -errno.ENOSYS
    
  def release ( self, path, flags ):
    #print '*** release', path, flags
    return 0
    return -errno.ENOSYS
        
  def fsync ( self, path, isFsyncFile ):
    #print '*** fsync', path, isFsyncFile
    return -errno.ENOSYS
    
  def statfs ( self ):
    #print '*** statfs'
    return -errno.ENOSYS
    
  def utime ( self, path, times ):
    #print '*** utime', path, times
    #return -errno.ENOSYS
    node = self.find_node(path)
    if node:
      f = node['file']
      print 'GAH', f.name
      return os.utime(f.name, times)
    return -errno.ENOSYS
        
if __name__ == '__main__':  
  fs = MyFS()  
  fs.parse(errex=1)  
  fs.main()  
